﻿using System;
using System.Globalization;
using QAToolsAddin.Comparer;
using QAToolsAddin.Components;
using QAToolsAddin.GenericDialogs;
using Excel = Microsoft.Office.Interop.Excel;

namespace QAToolsAddin.CustomDialogs {
    public partial class FrmCompare : ExcelForm {
        private readonly Excel.Application _excelapp;
        private Excel.Workbook _workbook;
        private Excel.Worksheet _worksheet;
        private readonly object Missing = Type.Missing;

        public FrmCompare(Excel.Application excelapp, CultureInfo culture)
            : base(excelapp) {
            InitializeComponent();
            _excelapp = excelapp;
            ctrlWorkbookA.SelectionChanged += ctrlWorkbookA_SelectedIndexChanged;
            ctrlWorkbookB.SelectionChanged += ctrlWorkbookB_SelectedIndexChanged;
            ctrlRefA.WindowActivate += ctrlWorkbookA.SetWindow;
            ctrlRefB.WindowActivate += ctrlWorkbookB.SetWindow;
        }

        private void LoadSettings() {
            ctrlOptOppositeCells.Checked = (bool)Settings.GetParam(this, "ctrlOptOppositeCells", true);
            ctrlOptDisorderedCells.Checked = (bool)Settings.GetParam(this, "ctrlOptDisorderedCells", false);
            ctrlOptRows.Checked = (bool)Settings.GetParam(this, "ctrlOptRows", false);
            ctrlIgnoreCase.Checked = (bool)Settings.GetParam(this, "ctrlIgnoreCase", false);
            ctrlUseFormula.Checked = (bool)Settings.GetParam(this, "ctrlUseFormula", false);
            ctrlOptSingleMatch.Checked = (bool)Settings.GetParam(this, "ctrlOptSingleMatch", false);
            ctrlInteriorColor.SelectedColor = (int)Settings.GetParam(this, "ctrlInteriorColor", 0xFF);
            ctrlFontColor.SelectedColor = (int)Settings.GetParam(this, "ctrlFontColor", 0xFFFFFF);
            ctrlOptCurrent.Checked = (bool)Settings.GetParam(this, "ctrlOptCurrent", false);
            ctrlOptNewSheet.Checked = (bool)Settings.GetParam(this, "ctrlOptNewSheet", true);
            ctrlOptSBSSelections.Checked = (bool)Settings.GetParam(this, "ctrlOptSBSSelections", true);
            ctrlOptSBSColumns.Checked = (bool)Settings.GetParam(this, "ctrlOptSBSColumns", false);
            ctrlOptSBSValues.Checked = (bool)Settings.GetParam(this, "ctrlOptSBSValues", false);
        }

        private void SaveSettings() {
            Settings.SetParam(this, "ctrlOptOppositeCells", ctrlOptOppositeCells.Checked);
            Settings.SetParam(this, "ctrlOptDisorderedCells", ctrlOptDisorderedCells.Checked);
            Settings.SetParam(this, "ctrlOptRows", ctrlOptRows.Checked);
            Settings.SetParam(this, "ctrlIgnoreCase", ctrlIgnoreCase.Checked);
            Settings.SetParam(this, "ctrlUseFormula", ctrlUseFormula.Checked);
            Settings.SetParam(this, "ctrlOptSingleMatch", ctrlOptSingleMatch.Checked);
            Settings.SetParam(this, "ctrlInteriorColor", ctrlInteriorColor.SelectedColor);
            Settings.SetParam(this, "ctrlFontColor", ctrlFontColor.SelectedColor);
            Settings.SetParam(this, "ctrlOptCurrent", ctrlOptCurrent.Checked);
            Settings.SetParam(this, "ctrlOptNewSheet", ctrlOptNewSheet.Checked);
            Settings.SetParam(this, "ctrlOptSBSSelections", ctrlOptSBSSelections.Checked);
            Settings.SetParam(this, "ctrlOptSBSColumns", ctrlOptSBSColumns.Checked);
            Settings.SetParam(this, "ctrlOptSBSValues", ctrlOptSBSValues.Checked);
        }

        private void ApplyFieldsRules() {
            SuspendLayout();

            bool optionRow = ctrlOptRows.Checked;
            lblKeysA.Enabled = optionRow;
            ctrlKeys.Enabled = optionRow;
            ctrlWithHeadersA.Enabled = optionRow;
            ctrlWithHeadersB.Enabled = optionRow;

            bool disposition = ctrlOptRows.Checked && ctrlOptNewSheet.Checked;
            ctrlOptSBSColumns.Enabled = disposition;
            ctrlOptSBSSelections.Enabled = disposition;
            ctrlOptSBSValues.Enabled = disposition;

            bool multimatch = ctrlOptOppositeCells.Checked == false;
            ctrlOptSingleMatch.Enabled = multimatch;

            ResumeLayout();

        }

        private void FrmCompare_Load(object sender, EventArgs e) {
            Excel.Range sel1 = null, sel2 = null;
            _workbook = _excelapp.ActiveWorkbook;
            _worksheet = (Excel.Worksheet)_excelapp.ActiveSheet;
            if (_workbook == null || _worksheet == null)
                throw new InputError("Open a workbook first!");

            SuspendLayout();
            LoadSettings();
            ApplyFieldsRules();

            int nbSelectedSheet = _excelapp.ActiveWindow.SelectedSheets.Count;
            if (nbSelectedSheet < 3) {
                if (nbSelectedSheet == 1) {
                    var selection = _excelapp.ActiveWindow.RangeSelection;
                    int nbarea = selection.Areas.Count;
                    if (nbarea == 1) {
                        if (selection.Worksheet.UsedRange.Cells.Count != 1)
                            sel1 = Utils.ExtendRange(selection);
                    } else {
                        sel1 = Utils.ExtendRange(selection.Areas[1]);
                        sel2 = Utils.ExtendRange(selection.Areas[2]);
                    }
                } else {
                    sel1 = ((Excel.Worksheet)_excelapp.ActiveWindow.SelectedSheets[1]).UsedRange;
                    sel2 = ((Excel.Worksheet)_excelapp.ActiveWindow.SelectedSheets[2]).UsedRange;
                }

                ctrlWithHeadersA.Checked = Utils.RangeHasHeaders(sel1);
                ctrlWithHeadersB.Checked = Utils.RangeHasHeaders(sel2);
                if (sel1 != null)
                    ctrlRefA.Range = sel1;
                if (sel2 != null)
                    ctrlRefB.Range = sel2;

            } else {
                throw new InputError("You can't select more than 2 sheets!");
            }
            ResumeLayout();
        }

        private void UpdateKeys_Delegate(object sender, EventArgs e) {
            if (ctrlOptRows.Checked)
                ctrlKeys.SetColumnSelector(ctrlRefA.Range, ctrlWithHeadersA.Checked);
        }

        private void btClose_Click(object sender, EventArgs e) {
            Close();
        }

        private void btCompare_Click(object sender, EventArgs e) {
            try {
                Utils.SwitchExcelToIdle(_excelapp, false);
                RunCompare();
            } catch (InputError ex) {
                MsgBox.ShowError(_excelapp, ex.Message);
            } finally {
                Utils.SwitchExcelToIdle(_excelapp, true);
            }
        }

        private void RunCompare() {
            if (ctrlRefA.IsEmpty) throw new InputError("Selection 1 is empty or incorrect.");
            if (ctrlRefB.IsEmpty) throw new InputError("Selection 2 is empty or incorrect.");

            //Shrink to used range
            Excel.Range rgSel1 = _excelapp.Intersect(ctrlRefA.Range, ctrlRefA.Range.Worksheet.UsedRange);
            if (rgSel1 == null) throw new InputError("Selection 1 is incorrect!");
            Excel.Range rgSel2 = _excelapp.Intersect(ctrlRefB.Range, ctrlRefB.Range.Worksheet.UsedRange);
            if (rgSel2 == null) throw new InputError("Selection 2 is incorrect!");

            //Define data range
            int nbRow1 = rgSel1.Rows.Count, nbCol1 = rgSel1.Columns.Count;
            int nbRow2 = rgSel2.Rows.Count, nbCol2 = rgSel2.Columns.Count;
            int offsetRow1 = ctrlOptRows.Checked && ctrlWithHeadersA.Checked ? 1 : 0;
            int offsetRow2 = ctrlOptRows.Checked && ctrlWithHeadersB.Checked ? 1 : 0;
            Excel.Range rgData1 = rgSel1.Offset[offsetRow1, Type.Missing].Resize[nbRow1 - offsetRow1, Type.Missing];
            Excel.Range rgData2 = rgSel2.Offset[offsetRow2, Type.Missing].Resize[nbRow2 - offsetRow2, Type.Missing];
            if (rgData1 == null) throw new InputError("Selection 1 is empty.");
            if (rgData2 == null) throw new InputError("Selection 2 is empty.");
            if (rgData1.Cells.Count < 2) throw new InputError("Selection 1 must contain at least 2 cells.");
            if (rgData2.Cells.Count < 2) throw new InputError("Selection 2 must contain at least 2 cells.");

            //read data
            bool optionMulti = ctrlOptSingleMatch.Checked == false;
            bool optionformula = ctrlUseFormula.Checked;
            object[,] data1 = Utils.GetDataFromRange(rgData1, ctrlUseFormula.Checked);
            object[,] data2 = Utils.GetDataFromRange(rgData2, ctrlUseFormula.Checked);
            object[,] titles1 = Utils.GetTitlesFromRange(rgSel1, ctrlWithHeadersA.Checked);
            object[,] titles2 = Utils.GetTitlesFromRange(rgSel2, ctrlWithHeadersB.Checked);
            object[] formats1 = Utils.GetColumnsFormating(rgData1);
            object[] formats2 = Utils.GetColumnsFormating(rgData2);

            //Initilise caompare class and save seeting
            var compare = new Compare { IgnoreCase = ctrlIgnoreCase.Checked };
            Excel._Worksheet worksheet;
            XlAlign optionAlign = XlAlign.None;
            object[] res = { };
            SaveSettings();

            //compare
            if (ctrlOptRows.Checked) {
                int[] keysColA = ctrlKeys.IsEmpty ? ctrlKeys.UncheckedIds : ctrlKeys.CheckedIds;
                int[] valColA = Utils.GetVisibleColumnsPosition(rgData1, null, keysColA);
                int[] valColB = Utils.GetVisibleColumnsPosition(rgData2, null, keysColA);
                if (ctrlOptCurrent.Checked) {
                    res = compare.CompareRowsStatic(ref data1, ref data2, keysColA, keysColA, valColA, valColB, optionMulti);
                } else if (ctrlOptNewSheet.Checked) {
                    if (ctrlOptSBSSelections.Checked) optionAlign = XlAlign.Tables;
                    else if (ctrlOptSBSColumns.Checked) optionAlign = XlAlign.Columns;
                    else if (ctrlOptSBSValues.Checked) optionAlign = XlAlign.Values;
                    res = compare.CompareRowsAligned(ref data1, ref data2, ref titles1, ref titles2, keysColA, keysColA, valColA, valColB, optionMulti, optionAlign);
                }
            } else {
                if (ctrlOptDisorderedCells.Checked)
                    res = compare.CompareListOfCells(ref data1, ref data2, optionMulti);
                else if (ctrlOptOppositeCells.Checked)
                    res = compare.CompareOppositeCells(ref data1, ref data2);
            }

            //Add new sheet, formats and headers
            var res1 = res.Length != 0 ? (CellSet)res[0] : null;
            var res2 = res.Length > 1 ? (CellSet)res[1] : null;
            if (ctrlOptNewSheet.Checked) {
                worksheet = (Excel.Worksheet)_workbook.Sheets.Add(Missing, _workbook.Sheets[_workbook.Sheets.Count], Missing, Missing);
                //set data ranges
                int offsetCol = optionAlign == XlAlign.None ? 0 : 1;
                rgData1 = ((Excel.Range)worksheet.Cells[2, 1 + offsetCol]).Resize[res1.RowLen, res1.ColLen];
                if (res2 != null)
                    rgData2 = ((Excel.Range)worksheet.Cells[2, 2 + offsetCol + res1.ColLen]).Resize[res2.RowLen, res2.ColLen];
                else
                    rgData2 = null;
                //set formats
                if (optionAlign == XlAlign.Tables || optionAlign == XlAlign.None) {
                    Utils.SetColumnsFormat(rgData1, res1.RowLen, formats1, 1, 1);
                    Utils.SetColumnsFormat(rgData2, res2.RowLen, formats2, 1, 1);
                } else if (optionAlign == XlAlign.Columns) {
                    Utils.SetColumnsFormat(rgData1, res1.RowLen, formats1, 1, 2);
                    Utils.SetColumnsFormat(rgData1, res1.RowLen, formats2, 2, 2);
                }
                //set col info
                if (optionAlign != XlAlign.None) {
                    AddColumnInfoToWorksheet((RowSet)res1, rgData1);
                    if (optionAlign == XlAlign.Tables)
                        AddColumnInfoToWorksheet((RowSet)res2, rgData2);
                }

                //Add data and titles
                if (ctrlOptRows.Checked) {
                    Utils.AddDataToRange(rgData1, res1.Data, optionformula, ((RowSet)res1).Titles);
                    if (res2 != null)
                        Utils.AddDataToRange(rgData2, res2.Data, optionformula, ((RowSet)res2).Titles);
                    worksheet.UsedRange.AutoFilter(1, Type.Missing, Excel.XlAutoFilterOperator.xlAnd, Type.Missing, true);
                } else {
                    Utils.AddDataToRange(rgData1, res1.Data, optionformula, titles1);
                    Utils.AddDataToRange(rgData2, res2.Data, optionformula, titles2);
                }
                worksheet.Activate();
            } else {
                ((Excel._Worksheet)rgData1.Worksheet).Activate();
            }

            Utils.AddTagsToRange(rgData1, res1.Diff, ctrlInteriorColor.SelectedColor, ctrlFontColor.SelectedColor);
            if (res2 != null)
                Utils.AddTagsToRange(rgData2, res2.Diff, ctrlInteriorColor.SelectedColor, ctrlFontColor.SelectedColor);

        }



        private void CompareTypeSelection_CheckedChanged(object sender, EventArgs e) {
            ApplyFieldsRules();
        }

        private void OutputSelection_CheckedChanged(object sender, EventArgs e) {
            ApplyFieldsRules();
        }

        private void AddDataToWorksheet(RowSet resultset, Excel.Range target, bool useFormula) {
            var rgTitles = target.Resize[1, resultset.ColLen];
            Utils.AddTitlesToRange(rgTitles, resultset.Titles);
            var rgData = ((Excel.Range)target.Cells[2, 1]).Resize[resultset.RowLen, resultset.ColLen];
            Utils.AddDataToRange(rgData, resultset.Data, useFormula);
            Utils.AddTagsToRange(rgData, resultset.Diff, ctrlInteriorColor.SelectedColor, ctrlFontColor.SelectedColor);
        }

        private void AddTitlesToRange(Excel.Range target, object[,] data) {
            var rgTitles = (Excel.Range)target.Rows[0];
            rgTitles.Value = data;
            rgTitles.Font.Bold = true;
        }

        private void AddColumnInfoToWorksheet(RowSet resultset, Excel.Range target) {
            object[,] info = ExcelArray<object>.Create(resultset.RowLen, 1);
            var rgTitles = (Excel.Range)target.Rows[0];
            var rgInfo = (Excel.Range)target.Columns[0];
            for (int ri = 1; ri <= resultset.RowLen; ri++) {
                switch (resultset.Match[ri]) {
                    case XlRowMatch.MatchWithDiff: info[ri, 1] = "'!="; break;
                    case XlRowMatch.NoMatch: info[ri, 1] = "'+"; break;
                }
            }
            Utils.SetCellStyle(rgInfo, 0xFF);
            rgTitles.Value = resultset.Titles;
            rgTitles.Font.Bold = true;
            rgInfo.Value = info;
            rgInfo.HorizontalAlignment = Excel.Constants.xlCenter;
            rgInfo.ColumnWidth = 2.5;
        }

        private void ctrlWorkbookA_SelectedIndexChanged(object sender, EventArgs e) {
            var window = ctrlWorkbookA.SelectedWindow;
            ctrlRefA.Workbook = (Excel.Workbook)window.Parent;
            ctrlKeys.Items = null;
            window.Activate();
            ctrlRefA.Focus();
        }

        private void ctrlWorkbookB_SelectedIndexChanged(object sender, EventArgs e) {
            var window = ctrlWorkbookB.SelectedWindow;
            ctrlRefB.Workbook = (Excel.Workbook)window.Parent;
            window.Activate();
            ctrlRefB.Focus();
        }

        private void ctrlRef_Escape(object sender, EventArgs e) {
            btClose.Focus();
        }



    }
}
